import datetime
import json
import re

from flask import request, flash, Response
from flask_appbuilder import expose, has_access, SimpleFormView
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
from flask_appbuilder.forms import DynamicForm
from flask_appbuilder.models.sqla.filters import FilterEqual
from flask_appbuilder.models.sqla.interface import SQLAInterface
from werkzeug.utils import redirect

from app import appbuilder, socketio, app
from app.models import BackEnd
from wtforms import IntegerField, SubmitField, StringField

from app.tasks.Proxy import Proxy, run_proxy


class FrontEndForm(DynamicForm):
    address = StringField(label="监听地址", widget=BS3TextFieldWidget())
    port = IntegerField(label="监听端口", widget=BS3TextFieldWidget())
    tasks = IntegerField(label="线程数量", widget=BS3TextFieldWidget())

    submit = SubmitField(
        label="确定",
        render_kw={
            "class": "btn btn-primary btn-block btn-flat",
        }
    )


MAX_POINTS_OF_CHART = 200

class RunningView(SimpleFormView):
    route_base = '/running'
    proxy = Proxy()
    form_template = "form_global.html"
    form = FrontEndForm
    form_title = '全局设置'

    echart_data = dict(x=[], queue=[], requests=[])

    global_cfg = None
    status_of_backends = dict()

    @expose("/health", methods=("POST",))
    def update_health(self):
        if not request.base_url.startswith(app.base_url) or request.remote_addr != '127.0.0.1':
            return ''
        backend = request.form.get("backend")
        health = request.form.get("health")
        data = self.status_of_backends.get(backend)
        if data:
            text = "fine-health" if health == 'True' else 'bad-health'
            data[0] = text
            socketio.emit("health", [backend, text])
        return 'ok'

    @expose("/health")
    @has_access
    def health(self):
        return Response(json.dumps(self.status_of_backends), mimetype='application/json')

    @expose("/stat", methods=("POST",))
    def update_statistics(self):
        if not request.base_url.startswith(app.base_url) or request.remote_addr != '127.0.0.1':
            return ''
        max_in_queue = request.form.get("max")
        requests_per_minute = request.form.get("cmds")
        print('max_in_quue', max_in_queue, 'requests_per_minute', requests_per_minute)
        self.echart_data['x'].append((datetime.datetime.now() - datetime.timedelta(seconds=60)).strftime("%H:%M"))
        self.echart_data['queue'].append(max_in_queue)
        self.echart_data['requests'].append(requests_per_minute)
        while len(self.echart_data['x']) > MAX_POINTS_OF_CHART:
            del self.echart_data['x'][0]
            del self.echart_data['queue'][0]
            del self.echart_data['requests'][0]
        socketio.emit("echart", 'refresh')
        return 'ok'

    @expose("/stat")
    @has_access
    def statistics(self):
        return Response(json.dumps(self.echart_data), mimetype='application/json')

    @expose("/exec", methods=("POST",))
    @has_access
    def exec(self):
        return self.run_core()

    @expose("/run", methods=("POST",))
    def run(self):
        if not request.base_url.startswith(app.base_url) or request.remote_addr != '127.0.0.1':
            return ''
        return self.run_core()

    def run_core(self):
        self.update_redirect()
        self._init_vars()
        form = self.form.refresh()

        address = form.data.get("address")
        port = form.data.get("port")
        tasks = form.data.get("tasks")
        pattern = re.compile("((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))")
        if address is None or pattern.match(address) is None:
            flash('IP地址输入错误', category='danger')
            return redirect(self.get_redirect())
        if port is None:
            flash('端口输入错误', category='danger')
            return redirect(self.get_redirect())
        if tasks is None:
            flash('线程数量输入错误', category='danger')
            return redirect(self.get_redirect())

        backends = RunningView.backends()
        if len(backends) == 0:
            flash('后端服务器列表为空', category='danger')
            return redirect(self.get_redirect())
        self.global_cfg = dict(addr=address, port=port, tasks=tasks)
        self.status_of_backends.clear()
        try:
            run_proxy(self.proxy, tasks, (address, port), backends, self.appbuilder.app.base_url)
            flash('启动代理成功', category='success')
            for host, port, desc in backends:
                self.status_of_backends["%s:%d" % (host, port)] = ["fine-health", desc]
        except Exception as e:
            flash('启动代理失败！发生异常：'+str(e), category='danger')
            return redirect(self.get_redirect())
        return redirect('/')

    @staticmethod
    def backends():
        datamodel = SQLAInterface(BackEnd, appbuilder.session)
        search_columns = ['active']
        _filters = datamodel.get_filters(search_columns)
        _filters.clear_filters()
        _filters.add_filter('active', FilterEqual, 1)
        count, rows = datamodel.query(_filters)
        lst = []
        for r in rows:
            lst.append((r.host, r.port, r.desc))
        return lst

    @expose('/stop')
    @has_access
    def stop(self):
        if self.proxy.is_alive():
            self.proxy.stop()
            socketio.emit('status', 'standby')
            RunningView.flash("停止代理成功！")
            self.global_cfg = None
        else:
            RunningView.flash("停止代理失败，当前代理处于关闭状态", 'warning')
        return 'ok'

    @expose('/pause')
    @has_access
    def pause(self):
        if self.proxy.is_alive():
            self.proxy.stop()
            socketio.emit('status', 'paused')
            RunningView.flash("暂停代理成功！")
        else:
            RunningView.flash("暂停代理失败，当前代理处于关闭状态", 'warning')
        return 'ok'

    @expose('/resume')
    @has_access
    def resume(self):
        if self.proxy is None or self.proxy.is_alive():
            RunningView.flash("无法将代理从暂停状态恢复", 'warning')
        else:
            backends = RunningView.backends()
            if len(backends) == 0:
                RunningView.flash('无法将代理从暂停状态恢复, 后端服务器列表为空', category='danger')
            else:
                try:
                    run_proxy(self.proxy, self.global_cfg.get('tasks'), (self.global_cfg.get('addr'), self.global_cfg.get('port')),
                              backends, self.appbuilder.app.base_url)
                    RunningView.flash('将代理从暂停状态恢复成功', category='success')
                    socketio.emit('status', 'running')
                except Exception as e:
                    RunningView.flash('将代理从暂停状态恢复失败！发生异常：' + str(e), category='danger')
        return 'ok'

    def form_get(self, form):
        if self.global_cfg is None:
            form.address.data = '0.0.0.0'
            form.port.data = 11211
            form.tasks.data = 32
        else:
            form.address.data = self.global_cfg.get('addr')
            form.port.data = self.global_cfg.get('port')
            form.tasks.data = self.global_cfg.get('tasks')

    @expose("/form", methods=['GET'])
    @has_access
    def this_form_get(self):
        self._init_vars()
        form = self.form.refresh()
        self.form_get(form)
        widgets = self._get_edit_widget(form=form)
        self.update_redirect()
        return self.render_template(self.form_template,
                                    title=self.form_title,
                                    widgets=widgets,
                                    appbuilder=self.appbuilder
                                    )

    @staticmethod
    def flash(content, category='success'):
        socketio.emit('flash', {'category':category, 'content':content})

    #methods for socketio
    @staticmethod
    @socketio.on('connect', namespace='/')
    def on_socketio_connect():
        me = RunningView.me()
        socketio.emit('global cfg', me.global_cfg)
        if me.proxy.is_alive():
            socketio.emit('status', 'running')
        else:
            if me.global_cfg:
                socketio.emit('status', 'paused')
            else:
                socketio.emit('status', 'standby')


    @staticmethod
    @socketio.on('disconnect', namespace='/')
    def on_socketio_disconnect():
        print('Client disconnected')

    @staticmethod
    def me():
        for view in appbuilder.baseviews:
            if isinstance(view, RunningView):
                return view
        return None


appbuilder.add_view_no_menu(RunningView, "RunningView")
